/******************************************************************
 * 作者：sipeiliu
 * 邮箱：sipeiliu@163.com
 * 描述：摄像头模块
 * 日期：2023.3.7
 ******************************************************************/
#include "/opt/sighter/inc/camera.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/fb.h>

int camera(void)
{
    /***1.打开摄像头***/
    static int fd; // 文件描述符

    fd = open("/dev/video0", O_RDWR);
    if (fd < 0)
    {
        printf("open error: 打开摄像头失败 \n");
        return -1;
    }

    /***2.查询摄像头属性***/
    struct v4l2_capability vcap = {0};

    ioctl(fd, VIDIOC_QUERYCAP, &vcap);
    if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        printf("device error: 此设备不是摄像头");
        return -1;
    }

    /***3.设置帧参数***/
    struct v4l2_fmtdesc fmtdesc = {0};     // 像素格式相关信息
    struct v4l2_frmsizeenum frmsize = {0}; // 帧分辨率相关信息
    struct v4l2_frmivalenum frmival = {0}; // 帧采集率相关信息
    struct v4l2_format fmt;                // 格式相关信息
    /*分辨率*/
    struct resolution
    {
        unsigned int width;  // 水平像素
        unsigned int height; // 垂直像素
    };

    /**查看摄像头支持的 像素格式**/
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("摄像头支持以下像素格式：\n");
    while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
    {
        printf("%d, 格式编号: 0x%x, 格式名称: %s \n",
               fmtdesc.index, fmtdesc.pixelformat, fmtdesc.description);
        fmtdesc.index++;
    }
    printf("\n");

    /**查看 指定像素格式 支持的 帧分辨率**/
    frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    /*Motion-JPEG格式 支持的帧分辨率*/
    frmsize.index = 0;
    frmsize.pixel_format = V4L2_PIX_FMT_MJPEG;
    struct resolution mjpeg[10];     // Motion-JPEG格式 帧分辨率数组
    memset(mjpeg, 0, sizeof(mjpeg)); // 初始化数组内存
    printf("Motion-JPEG格式 支持的帧分辨率(width x height): \n");
    while (0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
    {
        printf("%d x %d \n",
               frmsize.discrete.width, frmsize.discrete.height);
        /*帧分辨率存入数组*/
        mjpeg[frmsize.index].width = frmsize.discrete.width;
        mjpeg[frmsize.index].height = frmsize.discrete.height;
        frmsize.index++;
    }
    /*YUYV 4:2:2格式 支持的帧分辨率*/
    frmsize.index = 0;
    frmsize.pixel_format = V4L2_PIX_FMT_YUYV;
    struct resolution yuyv[10];    // YUYV 4:2:2格式 帧分辨率数组
    memset(yuyv, 0, sizeof(yuyv)); // 初始化数组内存
    printf("YUYV 4:2:2格式 支持的帧分辨率(width x height): \n");
    while (0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
    {
        printf("%d x %d \n",
               frmsize.discrete.width, frmsize.discrete.height);
        /*帧分辨率存入数组*/
        yuyv[frmsize.index].width = frmsize.discrete.width;
        yuyv[frmsize.index].height = frmsize.discrete.height;
        frmsize.index++;
    }
    printf("\n");

    /**查看 指定像素格式、指定帧分辨率 支持的 采集帧率**/
    /*Motion-JPEG格式*/
    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
    for (int i = 0; i < 10; i++)
    {
        frmival.index = 0;
        frmival.width = mjpeg[i].width;   // 指定帧水平像素
        frmival.height = mjpeg[i].height; // 指定帧垂直像素
        while (0 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival))
        {
            printf("Motion-JPEG格式, 分辨率: %d x %d, 帧率: %dfps \n",
                   frmival.width,
                   frmival.height,
                   frmival.discrete.denominator / frmival.discrete.numerator);
            frmival.index++;
        }
    }
    /*YUYV 4:2:2格式*/
    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    frmival.pixel_format = V4L2_PIX_FMT_YUYV;
    for (int i = 0; i < 10; i++)
    {
        frmival.index = 0;
        frmival.width = yuyv[i].width;   // 指定帧水平像素
        frmival.height = yuyv[i].height; // 指定帧垂直像素
        while (0 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival))
        {
            printf("YUYV 4:2:2格式, 分辨率: %d x %d, 帧率: %dfps \n",
                   frmival.width,
                   frmival.height,
                   frmival.discrete.denominator / frmival.discrete.numerator);
            frmival.index++;
        }
    }
    printf("\n");

    /**摄像头参数**/
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    /*获取格式信息*/
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0)
    {
        perror("ioctl error: 获取摄像头参数失败");
        return -1;
    }
    printf("摄像头当前参数: \n分辨率: %d x %d, 格式: 0x%x \n",
           fmt.fmt.pix.width,
           fmt.fmt.pix.height,
           fmt.fmt.pix.pixelformat);
    /*设置参数*/
    // fmt.fmt.pix.width = 960;
    // fmt.fmt.pix.height = 540;
    // fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //Motion-JPEG格式
    // if(ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
    // {
    //     perror("ioctl error: 设置摄像头参数失败");
    //     return -1;
    // }

    /***4.申请帧缓冲***/
    struct v4l2_requestbuffers reqbuf;

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.count = 2; // 申请帧缓冲数量
    reqbuf.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0)
    {
        printf("ioctl error:申请帧缓冲失败");
        return -1;
    }

    /***5.内存映射***/
    struct v4l2_buffer mapbuf;
    unsigned short *frm_base[reqbuf.count]; // 进程空间中帧的基地址

    mapbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for (mapbuf.index = 0; mapbuf.index < reqbuf.count; mapbuf.index++)
    {
        ioctl(fd, VIDIOC_QUERYBUF, &mapbuf); // 获取mapbuf信息
        frm_base[mapbuf.index] = mmap(NULL,  // 将帧缓冲映射到进程地址空间
                                      mapbuf.length,
                                      PROT_READ | PROT_WRITE,
                                      MAP_SHARED,
                                      fd,
                                      mapbuf.m.offset);

        if (MAP_FAILED == frm_base[mapbuf.index])
        {
            perror("mmap error: 内存映射失败");
            return -1;
        }
        /*入队*/
        if (ioctl(fd, VIDIOC_QBUF, &mapbuf) < 0)
        {
            perror("ioctl error: 帧缓冲入队失败");
            return -1;
        }
    }

    /***6.开启视频采集***/
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
    {
        perror("ioctl error: 开始视频采集失败");
        return -1;
    }

    /***7.数据处理***/
    struct v4l2_buffer readbuf;
    readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    while (1)
    {
        for (readbuf.index = 0; readbuf.index < reqbuf.count; readbuf.index++)
        {
            /*出队*/
            if (ioctl(fd, VIDIOC_DQBUF, &readbuf) < 0)
            {
                perror("ioctl error: 帧缓冲出队失败");
                return -1;
            }

            /*保存为图片*/
            FILE *file = fopen("picture.yuv", "w+");
            fwrite(frm_base[mapbuf.index], mapbuf.length, 1, file);
            fclose(file);

            /*入队*/
            if (ioctl(fd, VIDIOC_QBUF, &readbuf) < 0)
            {
                perror("ioctl error: 帧缓冲入队失败");
                return -1;
            }
        }
    }

    // /***8.结束视频采集***/
    // buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    // if(ioctl(fd, VIDIOC_STREAMOFF, &type))
    // {
    //     perror("ioctl error: 结束视频采集失败");
    //     return -1;
    // }
}